#include "player.hpp"
#include <sys/time.h>

int Player::drm_get_bpp(uint32_t format) const
{
    int bpp = 0;

    switch (format) {
	// case DRM_FORMAT_C1:
	// 	bpp = 1;
	// 	break;

	// case DRM_FORMAT_C2:
	// 	bpp = 2;
	// 	break;

	// case DRM_FORMAT_C4:
	// 	bpp = 4;
	// 	break;

	case DRM_FORMAT_C8:
	case DRM_FORMAT_NV12:
	case DRM_FORMAT_NV21:
	case DRM_FORMAT_NV16:
	case DRM_FORMAT_NV61:
	case DRM_FORMAT_NV24:
	case DRM_FORMAT_NV42:
	case DRM_FORMAT_YUV420:
	case DRM_FORMAT_YVU420:
		bpp = 8;
		break;

	// case DRM_FORMAT_NV15:
	// case DRM_FORMAT_NV20:
	// case DRM_FORMAT_NV30:
	// 	bpp = 10;
	// 	break;

	case DRM_FORMAT_ARGB4444:
	case DRM_FORMAT_XRGB4444:
	case DRM_FORMAT_ABGR4444:
	case DRM_FORMAT_XBGR4444:
	case DRM_FORMAT_RGBA4444:
	case DRM_FORMAT_RGBX4444:
	case DRM_FORMAT_BGRA4444:
	case DRM_FORMAT_BGRX4444:
	case DRM_FORMAT_ARGB1555:
	case DRM_FORMAT_XRGB1555:
	case DRM_FORMAT_XRGB1555 | DRM_FORMAT_BIG_ENDIAN:
	case DRM_FORMAT_ABGR1555:
	case DRM_FORMAT_XBGR1555:
	case DRM_FORMAT_RGBA5551:
	case DRM_FORMAT_RGBX5551:
	case DRM_FORMAT_BGRA5551:
	case DRM_FORMAT_BGRX5551:
	case DRM_FORMAT_RGB565:
	case DRM_FORMAT_RGB565 | DRM_FORMAT_BIG_ENDIAN:
	case DRM_FORMAT_BGR565:
	case DRM_FORMAT_UYVY:
	case DRM_FORMAT_VYUY:
	case DRM_FORMAT_YUYV:
	case DRM_FORMAT_YVYU:
		bpp = 16;
		break;

	case DRM_FORMAT_BGR888:
	case DRM_FORMAT_RGB888:
		bpp = 24;
		break;

	case DRM_FORMAT_ARGB8888:
	case DRM_FORMAT_XRGB8888:
	case DRM_FORMAT_ABGR8888:
	case DRM_FORMAT_XBGR8888:
	case DRM_FORMAT_RGBA8888:
	case DRM_FORMAT_RGBX8888:
	case DRM_FORMAT_BGRA8888:
	case DRM_FORMAT_BGRX8888:
	case DRM_FORMAT_ARGB2101010:
	case DRM_FORMAT_XRGB2101010:
	case DRM_FORMAT_ABGR2101010:
	case DRM_FORMAT_XBGR2101010:
	case DRM_FORMAT_RGBA1010102:
	case DRM_FORMAT_RGBX1010102:
	case DRM_FORMAT_BGRA1010102:
	case DRM_FORMAT_BGRX1010102:
		bpp = 32;
		break;

	case DRM_FORMAT_XRGB16161616F:
	case DRM_FORMAT_XBGR16161616F:
	case DRM_FORMAT_ARGB16161616F:
	case DRM_FORMAT_ABGR16161616F:
		bpp = 64;
		break;

	default:
		log_error("get bpp unsupported format 0x%08x\n",  format);
	}
    return bpp;
}

int Player::drm_create_fb(drm_buf_obj_t* bo)
{
	int ret;

	struct drm_mode_create_dumb create = {0};

	create.width = bo->width;
	create.height = bo->height;
	create.bpp = drm_get_bpp(m_drm_format);  // YUYV422 DRM_FORMAT_YUYV
	drmIoctl(m_fd, DRM_IOCTL_MODE_CREATE_DUMB, &create);
	log_debug("drm ioctl DRM_IOCTL_MODE_CREATE_DUMB, fmt:%d, bpp:%d, size:%d", 
			m_drm_format, create.bpp, create.size);

	bo->pitch = create.pitch;
	bo->size = create.size;
	bo->handle = create.handle;

	// bind the dumb-buffer to an FB object
	uint32_t handles[4] = {0}, pitches[4] = {0}, offsets[4] = {0};
	handles[0] = bo->handle;
	pitches[0] = bo->pitch;
	offsets[0] = 0;
	ret = drmModeAddFB2(m_fd, bo->width, bo->height, m_drm_format, 
			handles, pitches, offsets, &bo->fb_id, 0);
    if (ret) {
		log_error("failed to add fb (%ux%u): %s\n", bo->width, bo->height, strerror(errno));
		deinit();
		exit(EXIT_FAILURE);
	}

	// MAP
	struct drm_mode_map_dumb map = {0};
	map.handle = create.handle;
	drmIoctl(m_fd, DRM_IOCTL_MODE_MAP_DUMB, &map);

	bo->ptr = mmap(0, create.size, PROT_READ | PROT_WRITE,
			MAP_SHARED, m_fd, map.offset);
	if (bo->ptr == nullptr) {
        log_error("drm mmap dumb failed: %s.", strerror(errno));
        exit(EXIT_FAILURE);
    }

	// buf fd
	struct drm_prime_handle fd_args = {
		.handle = create.handle,
		.flags = 0,
		.fd = -1,
	};

	ret = drmIoctl(m_fd, DRM_IOCTL_PRIME_HANDLE_TO_FD, &fd_args);
    if (ret)
    {
        log_error("rk-debug handle_to_fd failed ret=%d,err=%s, handle=%x \n", ret, strerror(errno), fd_args.handle);
        exit(EXIT_FAILURE);
    }
	bo->buf_fd = fd_args.fd;

	return 0;
}

int Player::drm_destroy_fb(drm_buf_obj_t* bo)
{
	struct drm_mode_destroy_dumb destroy = {0};
	drmModeRmFB(m_fd, bo->fb_id);
	munmap(bo->ptr, bo->size);
	destroy.handle = bo->handle;
	drmIoctl(m_fd, DRM_IOCTL_MODE_DESTROY_DUMB, &destroy);
	return 0;
}

typedef struct {
	uint32_t crtc_id;
	uint32_t fb_idx;
	uint32_t fb_id[3];

	uint64_t swap_count;
	// std::atomic_bool page_flip_pending;
} drm_event_para_t;
static drm_event_para_t drm_event_para;

static void drm_page_flip_handler(int fd, uint32_t frame, uint32_t sec, uint32_t usec, void* data)
{
	drm_event_para_t* event_para = (drm_event_para_t *)data;
	event_para->fb_idx = ((event_para->fb_idx+1)>2)?0:(event_para->fb_idx+1);  // 三个BUF：显示BUF、缓冲BUF、空闲BUF 轮转切换
	drmModePageFlip(fd, event_para->crtc_id, event_para->fb_id[event_para->fb_idx], DRM_MODE_PAGE_FLIP_EVENT, data);
	
	log_debug("page_flip_handler, fd: %d, crtc_id: %d, fb_idx(using): %d, fb_id: %d, swap_count: %d.", 
			fd, event_para->crtc_id, 
			event_para->fb_idx, event_para->fb_id[event_para->fb_idx], 
			++event_para->swap_count);
}

int Player::drm_init()
{
	// 释放资源
	deinit();
	m_loop_run = true;
	int ret;
	log_debug("drm init: w/h/fmt %d/%d/%d.", m_width, m_height, m_drm_format);

	// 1. 打开DRM设备
	m_fd = open("/dev/dri/card0", O_RDWR | O_CLOEXEC);
	if (m_fd < 0) {
		log_error("open DRM device failed: %s.", strerror(errno));
		deinit();
		exit(EXIT_FAILURE);
	}

	uint64_t cap = 0;
	ret = drmGetCap(m_fd, DRM_CAP_DUMB_BUFFER, &cap);  // DUMB能力查询
	if (ret || cap == 0) {
		log_error("driver doesn't support the dumb buffer API.");
		deinit();
		exit(EXIT_FAILURE);
	}

	// 获取DRM信息
	ret = drmSetClientCap(m_fd, DRM_CLIENT_CAP_UNIVERSAL_PLANES, 1);  // 使用通用平面功能
	log_info("set DRM_CLIENT_CAP_UNIVERSAL_PLANES ret: %d.", ret);

	m_res = drmModeGetResources(m_fd);
	if (!m_res) {
		log_error("drmModeGetResources failed: %s\n", strerror(errno));
		deinit();
		exit(EXIT_FAILURE);
	}

	m_crtc_id = m_res->crtcs[0];
	m_conn_id = m_res->connectors[0];

	log_info("crtc_id: %d, conn_id: %d.", m_crtc_id, m_conn_id);

	// m_conn = drmModeGetConnector(m_fd, m_conn_id);
	
	log_info("traverse crtcs, num: %d.", m_res->count_crtcs);
#if 1
	for (int i = 0; i < m_res->count_crtcs; ++i) {
		m_crtc = drmModeGetCrtc(m_fd, m_res->crtcs[i]);
		log_info("crtcs[%d], id: %d, w/h: %d/%d.", i, m_crtc->crtc_id, m_crtc->width, m_crtc->height);
		if (m_crtc && m_crtc->mode_valid) {
			if (m_crtc->width != m_width || m_crtc->height != m_height) {
				m_crtc->width = m_width;
				m_crtc->height = m_height;
				m_crtc->mode.vdisplay = m_width;
				m_crtc->mode.hdisplay = m_height;
				m_crtc_id = m_res->crtcs[i];
			}
		}
	}
#else
	m_crtc = drmModeGetCrtc(m_fd, m_crtc_id);
	m_crtc->width = width;
	m_crtc->height = height;
	m_crtc->mode.vdisplay = width;
	m_crtc->mode.hdisplay = height;
#endif

	// 获取conn模式
	m_conn_mode_id = 0;
	m_conn = drmModeGetConnector(m_fd, m_conn_id);
	if (m_conn == nullptr) {
		log_error("get conn failed.");
		deinit();
		exit(EXIT_FAILURE);
	}
	log_info("traverse conn mode, num: %d, target w/h: %d/%d.", m_conn->count_modes, m_width, m_height);
	for (int i = 0; i < m_conn->count_modes; ++i) {
		log_info("conn_id: %d, mode[%d] name: %s, hd/vd: %d/%d.", 
			m_conn_id, i, m_conn->modes[i].name, 
			m_conn->modes[i].hdisplay, m_conn->modes[i].vdisplay);
	}
	for (int i = 0; i < m_conn->count_modes; ++i) {
		if (m_conn->modes[i].hdisplay == m_width && m_conn->modes[i].vdisplay == m_height) {
			m_conn_mode_id = i;
			log_info("get suited conn_mode_id seccessed, id: %d.", m_conn_mode_id);
			break;
		} else if (i + 1 == m_conn->count_modes) {
			log_error("get suited conn_mode_id failed.");
			deinit();
			exit(EXIT_FAILURE);
		}
	}

	// 4. 创建FB
	m_buf_obj[0].width = m_conn->modes[m_conn_mode_id].hdisplay;
	m_buf_obj[0].height = m_conn->modes[m_conn_mode_id].vdisplay;

	m_buf_obj[1].width = m_conn->modes[m_conn_mode_id].hdisplay;
	m_buf_obj[1].height = m_conn->modes[m_conn_mode_id].vdisplay;

	m_buf_obj[2].width = m_conn->modes[m_conn_mode_id].hdisplay;
	m_buf_obj[2].height = m_conn->modes[m_conn_mode_id].vdisplay;

	drm_create_fb(&m_buf_obj[0]);
	drm_create_fb(&m_buf_obj[1]);
	drm_create_fb(&m_buf_obj[2]);
	log_debug("create fb w/h: %d/%d.", m_buf_obj[0].width, m_buf_obj[0].height);

	log_info("set drm mode, fd: %d, crtc_id: %d, fb_id[0]: %d, fb_id[1]: %d, fb_id[2]: %d, conn_id: %d, conn_modes[%d]", 
				m_fd, m_crtc_id, m_buf_obj[0].fb_id, m_buf_obj[1].fb_id, m_buf_obj[2].fb_id, m_conn_id, m_conn_mode_id);
	ret = drmModeSetCrtc(m_fd, m_crtc_id, m_buf_obj[0].fb_id, 0, 0, 
			&m_conn_id, 1, &m_conn->modes[m_conn_mode_id]);
	if (ret) {
		log_error("drmModeSetCrtc failed: %s, ret: %d.", strerror(errno), ret);
		// exit(EXIT_FAILURE);
	}

	// 设置事件驱动翻页
	memset(&m_event, 0, sizeof m_event);
	m_event.version = DRM_EVENT_CONTEXT_VERSION;
	m_event.vblank_handler = NULL;
	m_event.page_flip_handler = drm_page_flip_handler;

	drm_event_para.fb_idx = 0;
	drm_event_para.crtc_id = m_crtc_id;
	drm_event_para.fb_id[0] = m_buf_obj[0].fb_id;
	drm_event_para.fb_id[1] = m_buf_obj[1].fb_id;
	drm_event_para.fb_id[2] = m_buf_obj[2].fb_id;
	ret = drmModePageFlip(m_fd, m_crtc_id, m_buf_obj[drm_event_para.fb_idx].fb_id,
			DRM_MODE_PAGE_FLIP_EVENT, &drm_event_para);
	if (ret) {
		log_error("drmModePageFlip failed: %d.", ret);
	}
	
	return 0;
}

int Player::drm_deinit()
{
	m_loop_run = false;

	if (m_buf_obj[0].fb_id != 0) {
		drm_destroy_fb(&m_buf_obj[0]);
		drm_destroy_fb(&m_buf_obj[1]);
		drm_destroy_fb(&m_buf_obj[2]);

		memset(m_buf_obj, 0, 3 * sizeof(drm_buf_obj_t));
	}

	if (m_conn != nullptr) {
		drmModeFreeConnector(m_conn);
		m_conn = nullptr;
	}

	if (m_crtc != nullptr) {
		drmModeFreeCrtc(m_crtc);
		m_crtc = nullptr;
	}
	
	if (m_res != nullptr) {
		drmModeFreeResources(m_res);
		m_res = nullptr;
	}
	log_error("drm fd:%d release.", m_fd);
	if (m_fd >= 0) {
		close(m_fd);
		m_fd = -1;
	}

	return 0;
}

int Player::rga_init(RgaSURF_FORMAT dst_format)
{
	if (m_rga_src_handle != 0 || m_rga_dst_handle[0] != 0 || m_rga_dst_handle[1] != 0) {
		rga_deinit();
	}

	m_rga_dst_format = dst_format;

	m_rga_dst_handle[0] = importbuffer_fd(m_buf_obj[0].buf_fd, m_buf_obj[0].size);
	m_rga_dst_handle[1] = importbuffer_fd(m_buf_obj[1].buf_fd, m_buf_obj[1].size);
	m_rga_dst_handle[2] = importbuffer_fd(m_buf_obj[2].buf_fd, m_buf_obj[2].size);
	if (m_rga_dst_handle[0] == 0 || m_rga_dst_handle[1] == 0 || m_rga_dst_handle[2] == 0) {
		log_error("importbuffer_fd failed.");
	}

	return 0;
}

int Player::rga_cvtcolor(RgaSURF_FORMAT format, uint8_t* buffer)
{
	int ret;
	memset(&m_rga_src_rect, 0, sizeof(m_rga_src_rect));
    memset(&m_rga_src, 0, sizeof(m_rga_src));
	memset(&m_rga_dst_rect, 0, sizeof(m_rga_dst_rect));
    memset(&m_rga_dst, 0, sizeof(m_rga_dst));

	m_rga_src_handle = importbuffer_virtualaddr(buffer, m_width, m_height, format);
    if (m_rga_src_handle <= 0) {
        log_error("Failed to import virtualaddr for src channel!");
        return -1;
    }

	m_rga_src = wrapbuffer_handle(m_rga_src_handle, m_width, m_height, format);
	m_rga_dst = wrapbuffer_handle(m_rga_dst_handle[((drm_event_para.fb_idx+1)>2)?0:(drm_event_para.fb_idx+1)], m_width, m_height, m_rga_dst_format);
    // m_rga_dst = wrapbuffer_handle(m_rga_dst_handle[drm_event_para.fb_idx^1], m_width, m_height, m_rga_dst_format); 

    ret = imcheck(m_rga_src, m_rga_dst, m_rga_src_rect, m_rga_dst_rect);
    if (IM_STATUS_NOERROR != ret) {
        log_error("rga imcheck, check error! %s", imStrError((IM_STATUS)ret));
		return -1;
    }

	IM_STATUS STATUS = imcvtcolor(m_rga_src, m_rga_dst, format, m_rga_dst_format);
    // log_debug("rga imcvtcolor: %s.", imStrError(STATUS));
    if (STATUS != IM_STATUS_SUCCESS) {
        log_error("rga imresize failed: %s.", imStrError(STATUS));
        return -1;
    }

	if (m_rga_src_handle > 0) {
        releasebuffer_handle(m_rga_src_handle);
		m_rga_src_handle = 0;
	}

	return 0;
}

int Player::rga_deinit()
{
	if (m_rga_dst_handle[0] > 0) {
        releasebuffer_handle(m_rga_src_handle);
		m_rga_dst_handle[0] = 0;
	}

	if (m_rga_dst_handle[1] > 0) {
        releasebuffer_handle(m_rga_src_handle);
		m_rga_dst_handle[1] = 0;
	}
	
	if (m_rga_dst_handle[2] > 0) {
        releasebuffer_handle(m_rga_src_handle);
		m_rga_dst_handle[2] = 0;
	}
	
	return 0;
}

int Player::write_image(RgaSURF_FORMAT format, uint8_t* buffer)
{
	rga_cvtcolor(format, buffer);
	return 0;
}

int Player::player_image(uint32_t wait_time)
{
	int ret;
	struct timeval timeout = { .tv_sec = wait_time, .tv_usec = 0 };
	fd_set fds;

	FD_ZERO(&fds);
	FD_SET(m_fd, &fds);
	ret = select(m_fd + 1, &fds, NULL, NULL, &timeout);
	if (ret < 0) {
		log_error("page_flip error");
		return -1;
	} else if (ret == 0) {
		log_error("page_flip timeout");
		return -2;  // 超时
	} else if (FD_ISSET(m_fd, &fds)) {
		ret = drmHandleEvent(m_fd, &m_event);
		if (ret != 0) {
			log_error("drmHandleEvent failed: %d", ret);
			return -3;
		}
		return ret;
	}

	return -4;
}

